using Microsoft.Cci;
using Microsoft.FxCop.Sdk;
using Microsoft.FxCop.Sdk.Introspection;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

namespace Tyng.Rules.Windows.Forms
{
    internal abstract class ProperlyDeclareControls<T> : FieldRuleBase<T> where T : ProperlyDeclareControls<T>
    {
        Type[] _checkTypes;
        Type[] _ignoreTypes;

        protected ProperlyDeclareControls(Type[] checkTypes, Type[] ignoreTypes)
        {
            if (checkTypes == null) throw new ArgumentNullException("checkTypes");
            if (checkTypes == null) throw new ArgumentNullException("ignoreTypes");

            _checkTypes = checkTypes;
            _ignoreTypes = ignoreTypes;
        }

        protected override bool ShouldCheckField(Field field)
        {
            if (field.IsStatic) return false;

            TypeNode declaring = field.DeclaringType;

            if (!(declaring.IsDerivedFrom(TypeNode.GetTypeNode(typeof(UserControl))) || declaring.IsDerivedFrom(TypeNode.GetTypeNode(typeof(Form)))))
            {
                return false;
            }

            bool isDerived = false;

            foreach (Type t in _checkTypes)
            {
                TypeNode tn = TypeNode.GetTypeNode(t);

                if (object.ReferenceEquals(field.Type, tn) || field.Type.IsDerivedFrom(tn))
                {
                    isDerived = true;
                    break;
                }
            }

            if (isDerived)
            {
                foreach (Type t in _ignoreTypes)
                {
                    TypeNode tn = TypeNode.GetTypeNode(t);

                    if (object.ReferenceEquals(field.Type, tn) || field.Type.IsDerivedFrom(tn))
                    {
                        return false;
                    }
                }
            }

            return isDerived;
        }

        protected override ProblemCollection Check(Field field)
        {
            string controlName = field.Name.Name;
            bool isIButtonControl = field.Type.Interfaces.SearchFor((Interface) TypeNode.GetTypeNode(typeof(IButtonControl))) >= 0;

            //check where its referenced
            if (!CallGraph.IsAccessed(field))
            {
                Problems.Add(new Problem(GetNamedResolution("NoAccess", controlName), field));

                //return here because no need to analayze non designer methods
                return Problems;
            }

            Property accessedThrough = null;

            TypeNode atpAtt = TypeNode.GetTypeNode(typeof(AccessedThroughPropertyAttribute));
            AttributeNode atpN = field.GetAttribute(atpAtt);

            if (atpN != null)
            {
                AccessedThroughPropertyAttribute atp = atpN.GetRuntimeAttribute() as AccessedThroughPropertyAttribute;

                if (atp != null)
                {
                    accessedThrough = field.DeclaringType.GetProperty(Identifier.For(atp.PropertyName));
                    controlName = accessedThrough.Name.Name;
                }
            }

            MemberList members = field.DeclaringType.Members;

            bool found = false;
            Method initializeComponent = field.DeclaringType.GetMethod(Identifier.For("InitializeComponent"));

            //Check this types methods
            foreach (Member mem in members)
            {
                if (accessedThrough == null || (!object.ReferenceEquals(mem, accessedThrough.Getter) && !object.ReferenceEquals(mem, accessedThrough.Setter)))
                {
                    Method met = mem as Method;

                    if (met != null && !met.IsStatic)
                    {
                        //if its a method but not the designer method...
                        if (!object.ReferenceEquals(met, initializeComponent) && !met.IsCompilerControlled)
                        {
                            found = IsCalledIn(field, accessedThrough, met);
                        }
                        else if (isIButtonControl && object.ReferenceEquals(met, initializeComponent))
                        {
                            //accept and cancel buttons...
                            found = IsAcceptOrCancel(field, accessedThrough, met);
                        }
                    }
                }

                if (found) break;
            }

            //check callers of the property set and get if a property was generated...
            if (!found && accessedThrough != null)
            {
                MethodList getters = CallGraph.CallersFor(accessedThrough.Getter);

                foreach (Method met in getters)
                {
                    if (!object.ReferenceEquals(met.DeclaringType, field.DeclaringType))
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    MethodList setters = CallGraph.CallersFor(accessedThrough.Setter);

                    foreach (Method met in setters)
                    {
                        if (!object.ReferenceEquals(met.DeclaringType, field.DeclaringType))
                        {
                            found = true;
                            break;
                        }
                    }
                }
            }



            ////look for events
            //if (!found && accessedThrough != null)
            //{
            //    InstructionList instructions = accessedThrough.Setter.Instructions;

            //    for (int i = 0; i < instructions.Length; i++)
            //    {
            //        if (instructions[i].OpCode == OpCode.Ldvirtftn)
            //        {
            //            found = true;
            //            break;
            //        }
            //    }
            //}
            //else if (!found && initializeComponent != null && accessedThrough == null)
            //{
            //    //resolution: OnlyDesignerEvents
            //    InstructionList instructions = initializeComponent.Instructions;

            //    for (int i = 0; i < instructions.Length; i++)
            //    {
            //        //if(instructions[i].OpCode == OpCode.Ldvirtftn)
            //    }
            //}
            
            if (!found) Problems.Add(new Problem(GetNamedResolution("NoNonDesignerAccess", controlName), field.DeclaringType));

            return Problems;
        }

        private bool IsAcceptOrCancel(Field field, Property accessedThrough, Method met)
        {
            TypeNode form = TypeNode.GetTypeNode(typeof(Form));

            if (!(object.ReferenceEquals(met.DeclaringType, form) || met.DeclaringType.IsDerivedFrom(form)))
            {
                return false;
            }

            Method acceptSetter = form.GetProperty(Identifier.For("AcceptButton")).Setter;
            Method cancelSetter = form.GetProperty(Identifier.For("CancelButton")).Setter;

            for (int i = 0; i < met.Instructions.Length; i++)
            {
                Instruction target = met.Instructions[i];

                //TODO: maybe only check certain op codes...
                if (object.ReferenceEquals(target.Value, acceptSetter) || object.ReferenceEquals(target.Value, cancelSetter))
                {
                    Instruction previousTarget = met.Instructions[i - 1];

                    if (object.ReferenceEquals(previousTarget.Value, field) || (accessedThrough != null && (object.ReferenceEquals(previousTarget.Value, accessedThrough.Getter) || object.ReferenceEquals(previousTarget.Value, accessedThrough.Setter))))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private bool IsCalledIn(Field field, Property accessedThrough, Method met)
        {
            for (int i = 0; i < met.Instructions.Length; i++)
            {
                Instruction target = met.Instructions[i];

                //TODO: maybe only check certain op codes...
                if (object.ReferenceEquals(target.Value, field) || (accessedThrough != null && (object.ReferenceEquals(target.Value, accessedThrough.Getter) || object.ReferenceEquals(target.Value, accessedThrough.Setter))))
                {
                    return true;
                }
            }

            return false;
        }
    }
}
